# MATT (Microsoft 365 Conditional Access Triage Tool) # Secure Guard Consulting # Files will be saved to C:\Temp folder # 1. Install modules if missing if (-not (Get-Module -ListAvailable -Name Microsoft.Graph)) { Install-Module Microsoft.Graph -Scope CurrentUser -Force } if (-not (Get-Module -ListAvailable -Name ImportExcel)) { Install-Module ImportExcel -Scope CurrentUser -Force } Import-Module Microsoft.Graph Import-Module ImportExcel # 2. Connect to Microsoft Graph Connect-MgGraph -Scopes "Policy.Read.All", "Directory.Read.All", "Application.Read.All", "Group.Read.All", "User.Read.All" # 3. Preload directory objects Write-Host "📥 Loading directory references..." $userMap = @{}; Get-MgUser -All | ForEach-Object { $userMap[$_.Id] = $_.DisplayName } $groupMap = @{}; $groupMembers = @{} Get-MgGroup -All | ForEach-Object { $groupMap[$_.Id] = $_.DisplayName $groupMembers[$_.Id] = (Get-MgGroupMember -GroupId $_.Id -All | ForEach-Object { $_.DisplayName }) -join ", " } # ✅ Role Map with fallback to role templates $roleMap = @{} Get-MgDirectoryRole | ForEach-Object { $roleMap[$_.Id] = $_.DisplayName } Get-MgDirectoryRoleTemplate -All | ForEach-Object { if (-not $roleMap.ContainsKey($_.Id)) { $roleMap[$_.Id] = $_.DisplayName } } $appMap = @{}; Get-MgServicePrincipal -All | ForEach-Object { $appMap[$_.Id] = $_.DisplayName } $locationMap = @{} Get-MgIdentityConditionalAccessNamedLocation -All | ForEach-Object { $desc = $_.DisplayName if ($_.AdditionalProperties.'@odata.type' -like '*ipNamedLocation') { $desc += " (" + ($_.IpRanges | ForEach-Object { $_.CidrAddress }) -join ", " + ")" } elseif ($_.AdditionalProperties.'@odata.type' -like '*countryNamedLocation') { $desc += " (Countries: " + ($_.CountriesAndRegions -join ", ") + ")" } $locationMap[$_.Id] = $desc } function Resolve { param($ids, $map, $type = "id") if (-not $ids) { return "" } return ($ids | ForEach-Object { if ($map[$_]) { if ($type -eq "group" -and $groupMembers[$_]) { "$($map[$_]) (Members: $($groupMembers[$_]))" } else { $map[$_] } } else { $_ } }) -join ", " } # 4. Load CA policies $policies = Get-MgIdentityConditionalAccessPolicy $total = $policies.Count # Custom row order $preferredOrder = @( "State", "CreatedDateTime", "ModifiedDateTime", "IncludeUsers", "ExcludeUsers", "IncludeGroups", "ExcludeGroups", "IncludeRoles", "ExcludeRoles", "TargetResources", "IncludePlatforms", "ExcludePlatforms", "ClientAppTypes", "IncludeLocations", "ExcludeLocations", "DeviceStates", "SignInRiskLevels", "UserRiskLevels", "GrantControls", "SessionControls", "AuthenticationFlows: TransferMethods" ) $matrix = @{} foreach ($s in $preferredOrder) { $matrix[$s] = @{} } $i = 1 foreach ($policy in $policies) { $name = $policy.DisplayName Write-Host "➡️ [$i/$total] $name"; $i++ # Format dates $matrix["CreatedDateTime"][$name] = if ($policy.CreatedDateTime) { $policy.CreatedDateTime.ToString("yyyy-MM-dd HH:mm") } else { "" } $matrix["ModifiedDateTime"][$name] = if ($policy.ModifiedDateTime) { $policy.ModifiedDateTime.ToString("yyyy-MM-dd HH:mm") } else { "" } $matrix["State"][$name] = $policy.State $matrix["IncludeUsers"][$name] = Resolve $policy.Conditions.Users.IncludeUsers $userMap $matrix["ExcludeUsers"][$name] = Resolve $policy.Conditions.Users.ExcludeUsers $userMap $matrix["IncludeGroups"][$name] = Resolve $policy.Conditions.Users.IncludeGroups $groupMap "group" $matrix["ExcludeGroups"][$name] = Resolve $policy.Conditions.Users.ExcludeGroups $groupMap "group" $matrix["IncludeRoles"][$name] = Resolve $policy.Conditions.Users.IncludeRoles $roleMap $matrix["ExcludeRoles"][$name] = Resolve $policy.Conditions.Users.ExcludeRoles $roleMap $matrix["TargetResources"][$name] = if ($policy.Conditions.Applications.IncludeAllApplications) { "All Apps" } else { Resolve $policy.Conditions.Applications.IncludeApplications $appMap } $matrix["IncludePlatforms"][$name] = ($policy.Conditions.Platforms.IncludePlatforms -join ", ") $matrix["ExcludePlatforms"][$name] = ($policy.Conditions.Platforms.ExcludePlatforms -join ", ") $matrix["ClientAppTypes"][$name] = ($policy.Conditions.ClientAppTypes -join ", ") $matrix["IncludeLocations"][$name] = Resolve $policy.Conditions.Locations.IncludeLocations $locationMap $matrix["ExcludeLocations"][$name] = Resolve $policy.Conditions.Locations.ExcludeLocations $locationMap $matrix["DeviceStates"][$name] = ($policy.Conditions.DeviceStates.IncludeStates -join ", ") $matrix["SignInRiskLevels"][$name] = ($policy.Conditions.SignInRiskLevels -join ", ") $matrix["UserRiskLevels"][$name] = ($policy.Conditions.UserRiskLevels -join ", ") $matrix["GrantControls"][$name] = @( $policy.GrantControls.BuiltInControls $policy.GrantControls.TermsOfUse $policy.GrantControls.CustomAuthenticationFactors ) -join ", " # AuthenticationFlows if ($policy.Conditions.PSObject.Properties.Name -contains "AuthenticationFlows") { $af = $policy.Conditions.AuthenticationFlows if ($af.transferMethods) { $matrix["AuthenticationFlows: TransferMethods"][$name] = $af.transferMethods } } # SessionControls flatten $controls = $policy.SessionControls | ConvertTo-Json -Depth 5 | ConvertFrom-Json if ($controls) { foreach ($scName in $controls.PSObject.Properties.Name) { $control = $controls.$scName if ($null -ne $control) { if ($control -is [System.Collections.IDictionary] -or $control -is [PSCustomObject]) { foreach ($prop in $control.PSObject.Properties) { $settingName = "SessionControls: ${scName} - $($prop.Name)" if (-not $matrix[$settingName]) { $matrix[$settingName] = @{} } $matrix[$settingName][$name] = $prop.Value } } else { $settingName = "SessionControls: ${scName}" if (-not $matrix[$settingName]) { $matrix[$settingName] = @{} } $matrix[$settingName][$name] = $control } } } } } # 5. Sort: preferred settings first, then others $orderedSettings = @() foreach ($item in $preferredOrder) { $matches = $matrix.Keys | Where-Object { $_ -eq $item -or $_ -like "${item}:*" } $orderedSettings += $matches } $remaining = $matrix.Keys | Where-Object { ($_ -like "SessionControls:*") -and ($_ -notin $orderedSettings) } $orderedSettings += ($remaining | Sort-Object) # 6. Build final export table $final = @() foreach ($row in $orderedSettings) { $entry = [ordered]@{ "Setting" = $row } foreach ($col in $matrix[$row].Keys) { $entry[$col] = $matrix[$row][$col] } $final += New-Object PSObject -Property $entry } # 7. Export to Excel $path = "C:\Temp\CA_Matrix.xlsx" $final | Export-Excel -Path $path -AutoSize Write-Host "`n✅ Done! File saved to:`n$path" # 8. Disconnect and clear token cache Disconnect-MgGraph -ErrorAction SilentlyContinue Remove-Item "$env:USERPROFILE\.graph" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item "$env:LOCALAPPDATA\.IdentityService\msal.cache" -Force -ErrorAction SilentlyContinue Write-Host "`n🧹 Microsoft Graph token cache cleared. Reauthentication will be required on next run."